docs(pm-dispatch): read the verify-lock queue depth before each dispatch and hold at depth ≥ 2 (#14944 Q1 B) - #16271
Conversation
…tch and hold at depth >= 2 The dispatch cap counts cards in flight; the container-wide heavy-verify lock is one unit wide, and the measured exit-99 (NOT MEASURED) rate is a function of arrival queue depth alone: 1% / 23% / 36% / 56% at depth 1 / 2 / 3 / 4. The PM loop now reads `scripts/pm/os-verify-lock.sh --status` before the N-th dispatch and waits while the arrival depth (`queue N:` lines + the dev's own run; holder and parked slots not counted) would reach LOCK_DEPTH_HOLD = 2. The nominal `batch` cap is unchanged; `priority:p0` may still exceed `batch` but does not cross the depth hold. Both places carry the rule (SKILL.md and references/core-rules.md). The four added lines are paid inside the same file by four same-topic merges, so the line ratchet stays at its ceilings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
|
Skills-seat reading (2026-09-06T14:5xZ), for the devx seat and this PR's dev: this PR and flight A of the skills lane (PR #16339, Generated by Claude Code |
|
PR #16339 (flight A) merged at 2026-09-07T01:18:23Z as Generated by Claude Code |
Ruling recorded — the three open questions (director seat, governed batch G2, 2026-09-07)Maintainer reply, verbatim: 「16271 同意」 — the three answers as presented.
What is still owed before the maintainer merges (devx seat): the re-fold onto Generated by Claude Code |
…spatch-depth-check # Conflicts: # .claude/skills/pm-dispatch/SKILL.md
The merge of origin/main brought SKILL.md to 813 lines against a ceiling of 811: both sides were line-neutral against the merge base, but the folds this branch used to pay for its four depth-hold lines landed in regions main had re-folded independently, so the resolution kept both. Pay the +2 with two more same-topic folds inside the same file — no rule removed, no ceiling raised, no cross-file move: - the two seat invariants "循环必须能从全新会话恢复" and "PM 不写文件、不写代码" join on one line (112 bytes); the separator before 唯一例外 drops from ; to , so the exception still reads as the no-writing rule's, not the recovery one's. - the two conditional-clause items 拒收用例 and 过滤/谓词语义 join on one line (119 bytes) inside the enumeration they already belong to. Ratchet reads SKILL.md 811/811 headroom 0, widest table row 342 (pin 342), core-rules.md 150/150. Every prose line stays within the 120-byte budget. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TezFG8ZMrNH6n5VTNpPpdH
…spatch-depth-check #16557 landed on main and removed 「PM 不写文件、不写代码;唯一例外及其全部条件见 红线。」 from 全体座位的不变量 — the second half of the fold this branch had just used to pay for its fourth depth-hold line. Conflict resolved by taking main's side: main's removal stands, no line #16557 landed is dropped, and the branch pays the resulting +1 with one more same-topic fold inside the same file — 「经验教训散文不再入技能文本」 joins 「交接按收尾清单逐步走完,并 `list_triggers` 清点自设定时器」 on one line (120 bytes), both inside 收班与交接. Ratchet after the resolution: SKILL.md 811/811 headroom 0, widest table row 342 (pin 342), core-rules.md 150/150. The four depth-hold rules and the `priority:p0` state-table row are unchanged from what the PR states. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TezFG8ZMrNH6n5VTNpPpdH
Patch round: re-folded onto
|
| collision | what main did |
effect on this branch |
|---|---|---|
| #16339 + #16486/#16501 | re-folded 自退读数 and 同文件串行豁免 independently, staying at 811 | both of this PR's original folds in those regions were absorbed by main; the merge kept both sides ⇒ SKILL.md 813 / ceiling 811 |
| #16557 | deleted 「PM 不写文件、不写代码;唯一例外及其全部条件见红线。」 from 全体座位的不变量 | that line was the second half of the fold this branch had just made ⇒ conflict, and after resolving, 812 / 811 |
Both were paid the same way the ruling requires — same-topic folds inside the same file, no rule removed, no ceiling raised, no cross-file move, every prose line ≤ 120 bytes.
Fold payment as it now stands
Four added lines (the four depth-hold rules), four folds. Two are the PR's originals that main did not touch; two are new this round, replacing the two main absorbed or deleted.
| fold | joined | bytes |
|---|---|---|
| 收班与交接 (new) | 「经验教训散文不再入技能文本」 + 「交接按收尾清单逐步走完,并 list_triggers 清点自设定时器」 |
120 |
| 条件性标准条款 (new) | 「拒收用例 ⇒ code+status 最低断言」 + 「过滤/谓词语义 ⇒ 编译面清单逐面申报,⛔ 静默略过」 |
119 |
| 降档 (original) | 「降档的档位与理由记入认领评论 Container & model 行」 + 「档位逐次派发显式传参,永不省略」 | 113 |
| 跨墙 cron (original) | 「第一枪成功的 fire 跑恢复后删除 cron」 + 「fired 文本照定时器写法纪律」 | 89 |
The conflict with #16557 was resolved by taking main's side: its removal stands, and no line #16557 or #16339 landed was dropped. git diff origin/main on the two governed files is 11 insertions / 11 deletions — line-neutral, as it was designed to be.
The four depth-hold rules and the priority:p0 state-table row are byte-identical to what the PR states. The depth-counting one-liner in the PR body still matches the merged text verbatim (到达深度 = queue N: 行数 + 1(待派 dev 的运行算作到达);state: holder 与 parked 行不计。, SKILL.md line 439), and the documented grep -c form was re-checked against live --status output this round (queue: empty ⇒ arrival depth 1).
Ratchet — live, at 06d3e0c95
✓ check-skill-line-ratchet self-test: 157 cases pass.
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 811 lines (ceiling 811; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md: widest table row is 342 bytes (pin 342; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/core-rules.md is 150 lines (ceiling 150; headroom 0).
Note the ratchet script itself moved on main this round (#16557 edited scripts/pm/check-skill-line-ratchet.mjs); the readings above are from that new script.
Gates — all 16, re-run on the merged head
Derived with node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack; change set still exactly the two governed files. Exit codes captured by redirect before any pipe.
--ran reconciliation: 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN.
| command | exit | verdict |
|---|---|---|
pnpm check:pm-skill-ratchet |
0 | 811/811, 150/150, pin 342 (above) |
pnpm check:pm-skill-id-lint |
0 | 26 file(s) clean (pattern /#[0-9]{3,}/g) |
pnpm check:pm-governed-prose |
0 | 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others |
pnpm check:skill-frame-sync |
0 | 2 copies of the decision frame are structurally isomorphic across 2 files |
pnpm check:nul-bytes |
0 | 75 assertions over a temp git repo (real scan() path) |
pnpm check:pm-governed-merges |
0 | 274 assertions self-test |
pnpm check:agent-test-spelling |
0 | 0 violations — 475 file(s) |
pnpm check:doc-authoring |
0 | 16 declared batteries, 198 cases registered |
pnpm check:watch-hint-literal |
0 | 66 declaration(s) across 4 rostered name(s) |
pnpm check:refd-timer-probe |
0 | 11 cases pass, negative controls included |
pnpm check:driver-memory-census |
0 | silent pass |
node scripts/check-closing-keyword-parity.mjs |
0 | silent pass |
node scripts/check-closing-keyword-parity.mjs --self-test |
0 | pass |
node scripts/check-comment-mask-corpus.mjs |
0 | pass |
node scripts/pm/check-governed-queue-guard.mjs --self-test |
0 | 144 cases pass |
pnpm --filter @objectstack/lint run check:doc-formula-expressions |
0 | 22 record-scoped formula example(s) across 434 files / 1371 TS blocks judged clean |
The last one first refused with exit 3 PREREQUISITE NOT MET (unbuilt @objectstack/lint — NOT MEASURED, not a red). Built through the shared lock twice, once per merge, so the reading is against the merged tree rather than a pre-merge dist/: VERDICT command-exit 0 · held the lock 324s · waited 82s, 4 turbo tasks. skip-changeset is already on the PR; no changeset is owed.
Two stale spots in the PR body — left for the maintainer, not edited
The body was not touched this round (a PATCH would also downgrade its session-URL footer), so two of its sections now under-describe the diff:
- The fold table still lists 自退读数 and 同文件串行豁免. Both are now
main's own folds; this branch's live payment is the four-row table above. - The gate table and ratchet readings are pinned to head
4d3194ba7. This comment supersedes them at06d3e0c95.
Say the word and the body is rewritten to match; leaving it as-is keeps the record of what was true when it was written.
One thing worth flagging
This is the third fold collision on the same two files in about 36 hours (#16339, #16486/#16501, #16557), each costing a patch round. The cause is structural, not anyone's mistake: SKILL.md sits at headroom 0, so every concurrent card that adds a line must invent its own fold, and any two seats folding in the same region collide by construction. Not filed as a card from here — it is protocol design and belongs to the skills lane or the director, not to this PR's scope.
Generated by Claude Code
…e-fold SKILL.md after #16271 One conflict, .claude/skills/pm-dispatch/SKILL.md: #16271 joined 拒收用例 and 过滤/谓词语义 on one line where this branch had folded the neighbouring 「前提先行写明:issue 正文是线索不是规格。」. Resolution keeps both: main's joined line stays, the fold stays. Both sides' content survives — #16271's four depth-hold lines, its priority:p0 row edit and its four same-file merges; this branch's Thread-read template line, its carried-assertion rule and its two folds. SKILL.md stays at 811 lines; references/core-rules.md is main's copy untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
Part of #14944.
Implements the Q1 B half of the maintainer ruling recorded on the card (comment
5548762263, director seat, 2026-09-05, maintainer 「同意」). The card does not close on this PR — it closes on the one-week--reportre-measure the ruling asks for. The Q3 A half (the dev waiting discipline in.claude/agents/os-dev.md) is thedomain:skillslane's: #16262 remains open and is not touched here.The ruling (quoted)
Depth table the ruling adopted (dev measurement
5547089539, 313 ledger records / 35.1 h): exit-99 (NOT MEASURED) rate 1.0% / 22.6% / 36.1% / 55.6% at arrival queue depth 1 / 2 / 3 / 4.What changes
.claude/skills/pm-dispatch/SKILL.md, 执行座位职责 → 候选与批次, directly under thebatchcap line — four rules:scripts/pm/os-verify-lock.sh --status; when the arrival depth would be ≥LOCK_DEPTH_HOLD(= 2) the dispatch waits.queue N:行数 + 1(待派 dev 的运行算作到达);state:holder 与parked行不计。」 — the number ofqueue N:lines in the--statusoutput, plus one for the run the dev about to be dispatched will bring. Thestate:holder line andparked N:lines are not counted.batch;priority:p0may still exceedbatchbut does not cross the depth hold.The
priority:p0state-table row gains 「深度等待」 among the things queue-jumping does not exempt.references/core-rules.mdcarries both changes in its one-line form (line 11 and line 39), per the skill's own two-place rule.Why the holder is not counted. The threshold is defined on the ledger's
depthcolumn, which is what the depth table was computed over. That column is read byqueue_live | wc -lright after the arriving call mints its own ticket, and a run's ticket is removed at the moment it acquires (rm -f "$TICKET"immediately afterHOLDING=1), so the holder is never inqueue_live.--reportstates the same: "queue depth on arrival (the arriving run INCLUDED — 1 means nobody was ahead)". Counting the holder would shift every reading by one and hold at what the table calls depth 1 (1.0% exit-99). The two readings the PM seat already took under this rule (holder + 1 waiter ⇒ hold at 08:31Z; holder + empty queue ⇒ depth 1 ⇒ dispatch at 08:49Z) agree with this counting.One-liner over
--statusoutput, for the seat (no new flag added to the script —--statusexposes no machine-readable depth, and wideningscripts/pm/**is outside this card's file surface):Live reading while writing this, 2026-09-06T08:57:48Z:
state: holder pid 4035, held 338s — pnpm --filter '@objectstack/client-react...' build·queue: empty⇒ waiters 0, arrival depth 1 (below the hold).Provenance placement.
pnpm check:pm-skill-id-lintrejects any#followed by three or more digits in the skill corpus (maintainer ruling 2026-08-12: a rule's provenance lives in the PR that landed it), so the card number cannot appear in the skill text. The in-text provenance is the depth table itself; this PR is where the card is named.Line ratchet — paid in the same file
All three pm-dispatch files sit at headroom 0. The four added lines are paid by four same-topic merges inside
SKILL.md(no rule removed, no ceiling raised, no cross-file move):Ratchet readings at head
4d3194ba7:SKILL.md is 811 lines (ceiling 811; headroom 0),widest table row is 342 bytes (pin 342; headroom 0);core-rules.md is 150 lines (ceiling 150; headroom 0). Every added or edited prose line is ≤ 120 bytes (max 118 B).Gates — pinned to head
4d3194ba7Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths; change set from the merge base8adb9e84a): 16 commands, identical to the path-derived list.--ranreconciliation:16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN. Exit codes captured by redirect before any pipe.pnpm check:pm-skill-ratchet✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 811 lines (ceiling 811; headroom 0).pnpm check:pm-skill-id-lint✓ check-skill-id-lint: 25 file(s) clean (pattern /#[0-9]{3,}/g).pnpm check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others.pnpm check:skill-frame-sync✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 filespnpm check:nul-bytes✓ check-nul-bytes --self-test: 75 assertions …pnpm check:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 461 file(s) …pnpm check:doc-authoring✓ doc authoring guard: 46 published skill files clean …pnpm check:watch-hint-literal✓ check-watch-hint-literal: 62 declaration(s) across 4 rostered name(s) …node scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-test✓ check-closing-keyword-parity --self-test: 24 assertions, 5 mutations …node scripts/check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 6202 files, 0 disagree, 0 unparseablenode scripts/pm/check-governed-queue-guard.mjs --self-test✓ check-governed-queue-guard self-test: 144 cases passpnpm check:refd-timer-probe✓ check-refd-timer-probe self-test: 11 cases passpnpm check:driver-memory-censuspnpm check:pm-governed-merges✓ check-governed-merges --self-test: 274 assertions …pnpm --filter @objectstack/lint run check:doc-formula-expressions✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 429 files / 1371 TS blocks judged cleanThe last gate first refused with exit 3
PREREQUISITE NOT MET(unbuilt@objectstack/formula/@objectstack/lint— NOT MEASURED, not a red); the prerequisites were built through the shared lock (os-verify-lock: VERDICT command-exit 0 · held the lock 181s · waited 0s, 4 turbo tasks incl.@objectstack/spec) and the gate re-run is the row above.Note from the derivation tool: the worktree is one commit behind
origin/main(6a1e38244, test-shard scripts andci.yml); that commit touches none of the edited files or the gates above, and the branch is left on the dispatch base so the readings stay pinned to one tree.Not done here, on purpose
scripts/pm/os-verify-lock.sh(--depth/--json): the file surface of this card is.claude/skills/pm-dispatch/**; the counting rule is the documented one-liner above.references/dispatch-runbook.md(headroom 0; the rule and the counting form fit inSKILL.md)..claude/agents/os-dev.mduntouched — Dev contract: the verify-lock waiting discipline (acquire once, stay blocked, always name OS_VERIFY_LOCK_SLOT) — the #14944 ruling's Q3 A half #16262 remains open.skip-changeset).Open questions for the maintainer (not decided here)
priority:p0vs the depth hold. The ruling is silent. Written here as: p0 still exceedsbatchbut does not cross the depth hold — the hold protects gate measurement, and a p0 card gains nothing from a NOT MEASURED gate. Overrule by editing one clause in each of the two files.LOCK_DEPTH_HOLD = 2is named in prose. The skill's existing convention for numbers it reads (CONTRACT_REVIEW_TIER,GOVERNED_APPROVERS) is a single source inscripts/pm/dispatch-gates.mjs/ the queue guard. Moving it there is ascripts/pm/**edit — outside this card's declared surface — so it is left in prose; say the word and it becomes a one-line constant plus a pin.batchdefault.SKILL.md's argument table still lists thebatchdefault as3while the ruling calls the nominal cap 5 (2026-09-03). Not changed here (the ruling says the cap is unchanged, not that the default is); flagged for the seat.维护者速读(草稿)
--status),到达深度 ≥ 2 就等,不派;batch名义上限 5 不变;priority:p0仍可超batch,但不越过这个等待。规则同时写进 SKILL.md 与 core-rules.md。--report回贴 exit-99 率,卡片以那次读数关闭。Generated by Claude Code